package leju.common.widget;


import java.io.InputStream;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.util.AttributeSet;
import android.widget.ImageView;

import com.leju.common.util.Logger;

public class ImageViewScaleTouchBase extends ImageView
{

	public interface OnBitmapChangedListener
	{

		void onBitmapChanged(Drawable drawable);
	};

	public static final String LOG_TAG = "image";

	private final int DEFAULT_SCALE_RATIO = 3;
	
	protected Matrix mBaseMatrix = new Matrix();
	protected Matrix mSuppMatrix = new Matrix();
	protected Handler mHandler = new Handler();
	protected Runnable mOnLayoutRunnable = null;
	protected float mMaxZoom;
	protected final Matrix mDisplayMatrix = new Matrix();
	protected final float[] mMatrixValues = new float[9];
	protected int mThisWidth = -1, mThisHeight = -1;
	protected boolean mFitToScreen = false;

	protected RectF mBitmapRect = new RectF();
	protected RectF mCenterRect = new RectF();
	protected RectF mScrollRect = new RectF();

	private OnBitmapChangedListener mListener;

	public ImageViewScaleTouchBase(Context context)
	{
		super(context);
		init();
	}

	public ImageViewScaleTouchBase(Context context, AttributeSet attrs)
	{
		super(context, attrs);
		init();
	}

	public void setOnBitmapChangedListener(OnBitmapChangedListener listener)
	{
		mListener = listener;
	}

	protected void init()
	{
		setScaleType(ImageView.ScaleType.MATRIX);
	}

	public void clear()
	{
		setImageBitmap(null, true);
	}

	public void setFitToScreen(boolean value)
	{
		if (value != mFitToScreen)
		{
			mFitToScreen = value;
			requestLayout();
		}
	}

	@Override
	protected void onLayout(boolean changed, int left, int top, int right,
			int bottom)
	{
		super.onLayout(changed, left, top, right, bottom);
		mThisWidth = right - left;
		mThisHeight = bottom - top;
		Runnable r = mOnLayoutRunnable;
		if (r != null)
		{
			mOnLayoutRunnable = null;
			r.run();
		}
		if (getDrawable() != null)
		{
			if (mFitToScreen)
				getProperBaseMatrix2(getDrawable(), mBaseMatrix);
			else
				getProperBaseMatrix(getDrawable(), mBaseMatrix);
			setImageMatrix(getImageViewMatrix());
		}
	}

	@Override
	public void setImageBitmap(Bitmap bm)
	{
		setImageBitmap(bm, true);
	}

	public void setImageBitmap(final Bitmap bitmap, final boolean reset)
	{
		setImageBitmap(bitmap, reset, null);
	}

	public void setImageBitmap(final Bitmap bitmap, final boolean reset,
			Matrix matrix)
	{
		setImageBitmap(bitmap, reset, matrix, DEFAULT_SCALE_RATIO);
	}

	public void setImageBitmap(final Bitmap bitmap, final boolean reset,
			Matrix matrix, float maxZoom)
	{
		if (bitmap != null)
			setImageDrawable(new FastBitmapDrawable(bitmap), reset, matrix,
					maxZoom);
		else
			setImageDrawable(null, reset, matrix, maxZoom);
	}

	@Override
	public void setImageDrawable(Drawable drawable)
	{
		Matrix matrix = getDisplayMatrix();
		super.setImageDrawable(drawable);
		setImageDrawable(drawable, true, matrix, DEFAULT_SCALE_RATIO);
		requestLayout();
	}

	public void setImageDrawable(final Drawable drawable, final boolean reset,
			final Matrix initial_matrix, final float maxZoom)
	{

		final int viewWidth = getWidth();

		if (viewWidth <= 0)
		{
			mOnLayoutRunnable = new Runnable()
			{

				public void run()
				{
					Logger.d("runrunrunrun");
					Logger.d("viewWidth="+viewWidth);
					setImageDrawable(drawable, reset, initial_matrix, maxZoom);
				}

			};
			return;
		}

		_setImageDrawable(drawable, reset, initial_matrix, maxZoom);
	}

	protected void _setImageDrawable(final Drawable drawable,
			final boolean reset, final Matrix initial_matrix,
			final float maxZoom)
	{

		Logger.d("_setImageDrawable_setImageDrawable");
		if (drawable != null)
		{
			if (mFitToScreen)
				getProperBaseMatrix2(drawable, mBaseMatrix);
			else
				getProperBaseMatrix(drawable, mBaseMatrix);
			super.setImageDrawable(drawable);
		} else
		{
			mBaseMatrix.reset();
			super.setImageDrawable(null);
		}

		if (reset)
		{
			mSuppMatrix.reset();
			if (initial_matrix != null)
			{
				mSuppMatrix = new Matrix(initial_matrix);
			}
		}

		setImageMatrix(getImageViewMatrix());

		if (maxZoom < 1)
			mMaxZoom = maxZoom();
		else
			mMaxZoom = maxZoom;

		onBitmapChanged(drawable);
	}

	protected void onBitmapChanged(final Drawable bitmap)
	{
		if (mListener != null)
		{
			mListener.onBitmapChanged(bitmap);
		}
	}

	protected float maxZoom()
	{
		final Drawable drawable = getDrawable();

		if (drawable == null)
		{
			return 1F;
		}

		float fw = (float) drawable.getIntrinsicWidth() / (float) mThisWidth;
		float fh = (float) drawable.getIntrinsicHeight() / (float) mThisHeight;
		float max = Math.max(fw, fh) * DEFAULT_SCALE_RATIO;
		return max;
	}

	public float getMaxZoom()
	{
		return mMaxZoom;
	}

	public Matrix getImageViewMatrix()
	{
		mDisplayMatrix.set(mBaseMatrix);
		mDisplayMatrix.postConcat(mSuppMatrix);
		return mDisplayMatrix;
	}

	public Matrix getDisplayMatrix()
	{
		return new Matrix(mSuppMatrix);
	}

	protected void getProperBaseMatrix(Drawable drawable, Matrix matrix)
	{
		float viewWidth = getWidth();
		float viewHeight = getHeight();
		float w = drawable.getIntrinsicWidth();
		float h = drawable.getIntrinsicHeight();
		matrix.reset();

		if (w > viewWidth || h > viewHeight)
		{
			float widthScale = Math.min(viewWidth / w, 2.0f);
			float heightScale = Math.min(viewHeight / h, 2.0f);
			float scale = Math.min(widthScale, heightScale);
			matrix.postScale(scale, scale);
			float tw = (viewWidth - w * scale) / 2.0f;
			float th = (viewHeight - h * scale) / 2.0f;
			matrix.postTranslate(tw, th);
		} else
		{
			float tw = (viewWidth - w) / 2.0f;
			float th = (viewHeight - h) / 2.0f;
			matrix.postTranslate(tw, th);
		}
	}

	protected void getProperBaseMatrix2(Drawable bitmap, Matrix matrix)
	{
		float viewWidth = getWidth();
		float viewHeight = getHeight();
		Logger.d("IMAGE getProperBaseMatrix2_view: height:" + viewHeight + ", width:" + viewWidth);
		float w = bitmap.getIntrinsicWidth();
		float h = bitmap.getIntrinsicHeight();
		Logger.d("IMAGE getProperBaseMatrix2_bitmap: height:" + h + ", width:" + w);
		matrix.reset();
		float widthScale = viewWidth / w;
		float heightScale = viewHeight / h;
		float scale = Math.min(widthScale, heightScale);
		Logger.d("IMAGE getProperBaseMatrix2_scale:" + scale);		
		matrix.postScale(scale, scale);
		matrix.postTranslate((viewWidth - w * scale) / 2,
				(viewHeight - h * scale) / 2);
	}

	protected float getValue(Matrix matrix, int whichValue)
	{
		matrix.getValues(mMatrixValues);
		return mMatrixValues[whichValue];
	}

	protected RectF getBitmapRect()
	{
		final Drawable drawable = getDrawable();

		if (drawable == null)
			return null;
		Matrix m = getImageViewMatrix();
		mBitmapRect.set(0, 0, drawable.getIntrinsicWidth(),
				drawable.getIntrinsicHeight());
		m.mapRect(mBitmapRect);
		return mBitmapRect;
	}

	protected float getScale(Matrix matrix)
	{
		return getValue(matrix, Matrix.MSCALE_X);
	}

	/*public float getRotation()
	{
		return 0;
	}*/

	public float getScale()
	{
		return getScale(mSuppMatrix);
	}

	protected void center(boolean horizontal, boolean vertical)
	{
		// LOG.i(LOG_TAG, "center");
		final Drawable drawable = getDrawable();

		if (drawable == null)
			return;
		RectF rect = getCenter(horizontal, vertical);
		if (rect.left != 0 || rect.top != 0)
		{
			postTranslate(rect.left, rect.top);
		}
	}

	protected RectF getCenter(boolean horizontal, boolean vertical)
	{
		final Drawable drawable = getDrawable();

		if (drawable == null)
			return new RectF(0, 0, 0, 0);

		RectF rect = getBitmapRect();
		float height = rect.height();
		float width = rect.width();
		float deltaX = 0, deltaY = 0;
		if (vertical)
		{
			int viewHeight = getHeight();
			if (height < viewHeight)
			{
				deltaY = (viewHeight - height) / 2 - rect.top;
			} else if (rect.top > 0)
			{
				deltaY = -rect.top;
			} else if (rect.bottom < viewHeight)
			{
				deltaY = getHeight() - rect.bottom;
			}
		}
		if (horizontal)
		{
			int viewWidth = getWidth();
			if (width < viewWidth)
			{
				deltaX = (viewWidth - width) / 2 - rect.left;
			} else if (rect.left > 0)
			{
				deltaX = -rect.left;
			} else if (rect.right < viewWidth)
			{
				deltaX = viewWidth - rect.right;
			}
		}
		mCenterRect.set(deltaX, deltaY, 0, 0);
		return mCenterRect;
	}

	protected void postTranslate(float deltaX, float deltaY)
	{
		mSuppMatrix.postTranslate(deltaX, deltaY);
		setImageMatrix(getImageViewMatrix());
	}

	protected void postScale(float scale, float centerX, float centerY)
	{
		mSuppMatrix.postScale(scale, scale, centerX, centerY);
		setImageMatrix(getImageViewMatrix());
	}

	protected void zoomTo(float scale)
	{
		float cx = getWidth() / 2F;
		float cy = getHeight() / 2F;
		zoomTo(scale, cx, cy);
	}

	public void zoomTo(float scale, float durationMs)
	{
		float cx = getWidth() / 2F;
		float cy = getHeight() / 2F;
		zoomTo(scale, cx, cy, durationMs);
	}

	protected void zoomTo(float scale, float centerX, float centerY)
	{
		// LOG.i(LOG_TAG, "zoomTo");

		if (scale > mMaxZoom)
			scale = mMaxZoom;
		float oldScale = getScale();
		float deltaScale = scale / oldScale;
		postScale(deltaScale, centerX, centerY);
		onZoom(getScale());
		center(true, true);
	}

	protected void onZoom(float scale)
	{
	}

	public void scrollBy(float x, float y)
	{
		panBy(x, y);
	}

	protected void panBy(double dx, double dy)
	{
		RectF rect = getBitmapRect();
		mScrollRect.set((float) dx, (float) dy, 0, 0);
		updateRect(rect, mScrollRect);
		postTranslate(mScrollRect.left, mScrollRect.top);
		center(true, true);
	}

	protected void updateRect(RectF bitmapRect, RectF scrollRect)
	{
		float width = getWidth();
		float height = getHeight();

		if(null == bitmapRect) return;
		
		if (bitmapRect.top >= 0 && bitmapRect.bottom <= height)
			scrollRect.top = 0;
		if (bitmapRect.left >= 0 && bitmapRect.right <= width)
			scrollRect.left = 0;
		if (bitmapRect.top + scrollRect.top >= 0 && bitmapRect.bottom > height)
			scrollRect.top = (int) (0 - bitmapRect.top);
		if (bitmapRect.bottom + scrollRect.top <= (height - 0)
				&& bitmapRect.top < 0)
			scrollRect.top = (int) ((height - 0) - bitmapRect.bottom);
		if (bitmapRect.left + scrollRect.left >= 0)
			scrollRect.left = (int) (0 - bitmapRect.left);
		if (bitmapRect.right + scrollRect.left <= (width - 0))
			scrollRect.left = (int) ((width - 0) - bitmapRect.right);
		// LOG.d( LOG_TAG, "scrollRect(2): " + scrollRect.toString() );
	}

	protected void scrollBy(float distanceX, float distanceY,
			final double durationMs)
	{
		final double dx = distanceX;
		final double dy = distanceY;
		final long startTime = System.currentTimeMillis();
		mHandler.post(new Runnable()
		{

			double old_x = 0;
			double old_y = 0;

			public void run()
			{
				long now = System.currentTimeMillis();
				double currentMs = Math.min(durationMs, now - startTime);
				double x = easeOut(currentMs, 0, dx, durationMs);
				double y = easeOut(currentMs, 0, dy, durationMs);
				panBy((x - old_x), (y - old_y));
				old_x = x;
				old_y = y;
				if (currentMs < durationMs)
				{
					mHandler.post(this);
				} else
				{
					RectF centerRect = getCenter(true, true);
					if (centerRect.left != 0 || centerRect.top != 0)
						scrollBy(centerRect.left, centerRect.top);
				}
			}
		});
	}

	protected void zoomTo(float scale, final float centerX,
			final float centerY, final float durationMs)
	{
		// Log.i( LOG_TAG, "zoomTo: " + scale + ", " + centerX + ": " + centerY
		// );
		final long startTime = System.currentTimeMillis();
		final float incrementPerMs = (scale - getScale()) / durationMs;
		final float oldScale = getScale();
		mHandler.post(new Runnable()
		{

			public void run()
			{
				long now = System.currentTimeMillis();
				float currentMs = Math.min(durationMs, now - startTime);
				float target = oldScale + (incrementPerMs * currentMs);
				zoomTo(target, centerX, centerY);
				if (currentMs < durationMs)
				{
					mHandler.post(this);
				} else
				{
					// if ( getScale() < 1f ) {}
				}
			}
		});
	}

	public double easeOut(double time, double start, double end, double duration)
	{
		return end * ((time = time / duration - 1.0) * time * time + 1.0)
				+ start;
	}

	public void dispose()
	{
		clear();
	}
	
	
	@Override
	protected void onSizeChanged(int w, int h, int oldw, int oldh) {
		super.onSizeChanged(w, h, oldw, oldh);
	}
/*
	Handler mHandler = new Handler(){
		
	};
	*/
	class FastBitmapDrawable extends Drawable {

		protected Bitmap mBitmap;
		protected Paint mPaint;

		public FastBitmapDrawable( Bitmap b ) {
			mBitmap = b;
			mPaint = new Paint();
			mPaint.setDither( true );
			mPaint.setFilterBitmap( true );
		}
		
		public FastBitmapDrawable( Resources res, InputStream is ){
			this(BitmapFactory.decodeStream(is));
		}

		@Override
		public void draw( Canvas canvas ) {
			canvas.drawBitmap( mBitmap, 0.0f, 0.0f, mPaint );
		}

		@Override
		public int getOpacity() {
			return PixelFormat.TRANSLUCENT;
		}

		@Override
		public void setAlpha( int alpha ) {
			mPaint.setAlpha( alpha );
		}

		@Override
		public void setColorFilter( ColorFilter cf ) {
			mPaint.setColorFilter( cf );
		}

		@Override
		public int getIntrinsicWidth() {
			return mBitmap.getWidth();
		}

		@Override
		public int getIntrinsicHeight() {
			return mBitmap.getHeight();
		}

		@Override
		public int getMinimumWidth() {
			return mBitmap.getWidth();
		}

		@Override
		public int getMinimumHeight() {
			return mBitmap.getHeight();
		}
		
		public void setAntiAlias( boolean value ){
			mPaint.setAntiAlias( value );
			invalidateSelf();
		}

		public Bitmap getBitmap() {
			return mBitmap;
		}
	}
}
